package com.mindskip.xzs.security;

import com.mindskip.xzs.domain.enums.RoleEnum;
import com.mindskip.xzs.properties.MyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;
import java.util.List;
import java.util.UUID;


@Configuration
@EnableWebSecurity
public class SecurityConfigurer {


    @Configuration
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {


        private final RestDetailsServiceImpl detailsService;

        private final LoginAuthenticationEntryPoint loginAuthenticationEntryPoint;

        private final RestAccessDeniedHandler restAccessDeniedHandler;

        private final RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;

        private final RestAuthenticationFailureHandler restAuthenticationFailureHandler;

        private final RestLogoutSuccessHandler restLogoutSuccessHandler;

        private final MyProperties myProperties;

        private final RestAuthenticationProvider restAuthenticationProvider;

        public FormLoginWebSecurityConfigurerAdapter(RestDetailsServiceImpl formDetailsService, LoginAuthenticationEntryPoint loginAuthenticationEntryPoint,
                                                     RestAccessDeniedHandler restAccessDeniedHandler, RestAuthenticationSuccessHandler restAuthenticationSuccessHandler, RestAuthenticationFailureHandler restAuthenticationFailureHandler, RestLogoutSuccessHandler restLogoutSuccessHandler, MyProperties myProperties, RestAuthenticationProvider restAuthenticationProvider) {
            this.detailsService = formDetailsService;
            this.loginAuthenticationEntryPoint = loginAuthenticationEntryPoint;
            this.restAccessDeniedHandler = restAccessDeniedHandler;
            this.restAuthenticationSuccessHandler = restAuthenticationSuccessHandler;
            this.restAuthenticationFailureHandler = restAuthenticationFailureHandler;
            this.restLogoutSuccessHandler = restLogoutSuccessHandler;
            this.myProperties = myProperties;
            this.restAuthenticationProvider = restAuthenticationProvider;
        }

        /*
         * 如果开发者先容器中注册了一个PasswordEncoder实例，那么无论是全局的AuthenticationManager还是局部的AuthenticationManager，
         * 都将使用该PasswordEncoder实例，如果开发者没有提供任何PasswordEncoder实例，那么无论是全局的AuthenticationManager还是局部的
         * AuthenticationManager，都将使用DelegatingPasswordEncoder实例。
         */
        //创建BCryptPasswordEncoder注入容器
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }

        @Bean
        public RememberMeServices rememberMeServices() {
            return new RestTokenBasedRememberMeServices(UUID.randomUUID().toString(), detailsService);
        }

        /*
         * 静态资源过滤
         * 解决Security访问Swagger2被拦截的问题；
         * */
        @Override
        public void configure(WebSecurity web) throws Exception {
            // allow Swagger URL to be accessed without authentication
            web.ignoring().antMatchers(
                    "/swagger-ui.html",
                    "/doc.html",
                    "/v2/api-docs", // swagger api json
                    "/swagger-resources/configuration/ui", // 用来获取支持的动作
                    "/swagger-resources", // 用来获取api-docs的URI
                    "/swagger-resources/configuration/security", // 安全选项
                    "/swagger-resources/**",
                    // 补充路径，近期在搭建swagger接口文档时，通过浏览器控制台发现该/webjars路径下的文件被拦截，故加上此过滤条件即可。(2020-10-23)
                    "/webjars/**"

            );
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {

            // 从yml配置文件中获取白名单
            List<String> securityIgnoreUrls = myProperties.getSecurityIgnoreUrls();
            String[] ignores = new String[securityIgnoreUrls.size()];

            http.addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling()
                // 添加自定义认证失败异常处理器
                .authenticationEntryPoint(loginAuthenticationEntryPoint)
                // 添加自定义鉴权失败异常处理器
                .accessDeniedHandler(restAccessDeniedHandler)
                .and()
                // 添加自定义的 AuthenticationProvider
                .authenticationProvider(restAuthenticationProvider)
                .authorizeRequests()
                .antMatchers(securityIgnoreUrls.toArray(ignores)).permitAll()
                //url级别权限管理
                .antMatchers("/api/admin/**").hasRole(RoleEnum.ADMIN.getName())
                .antMatchers("/api/student/**").hasRole(RoleEnum.STUDENT.getName())
                // 除上面外的所有其他请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .logout().logoutUrl("/api/user/logout").logoutSuccessHandler(restLogoutSuccessHandler).invalidateHttpSession(true)
                .and()
                .rememberMe().key(CookieConstant.getName()).tokenValiditySeconds(CookieConstant.getInterval())
                // 设置自动登录时使用自定义的RememberMeServices
                .rememberMeServices(rememberMeServices())
                .and()
                // 关闭csrf
                .csrf().disable()
                // 关闭X-Frame-Options响应头
                .headers().frameOptions().disable()
                .and()
                // 开启跨域配置
                .cors();
        }

        // 设置CorsConfigurationSource
        @Bean
        public CorsConfigurationSource corsConfigurationSource() {
            final CorsConfiguration configuration = new CorsConfiguration();
            configuration.setMaxAge(3600L);
            configuration.setAllowedOrigins(Collections.singletonList("*"));
            configuration.setAllowedMethods(Collections.singletonList("*"));
            configuration.setAllowCredentials(true);
            configuration.setAllowedHeaders(Collections.singletonList("*"));
            final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/api/**", configuration);
            return source;
        }

        // 设置自定义的RestLoginAuthenticationFilter
        @Bean
        public RestLoginAuthenticationFilter authenticationFilter() throws Exception {
            RestLoginAuthenticationFilter authenticationFilter = new RestLoginAuthenticationFilter();
            // 给RestLoginAuthenticationFilter设置一个局部AuthenticationManager
            authenticationFilter.setAuthenticationManager(authenticationManagerBean());
            // 设置认证成功处理器
            authenticationFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
            // 设置认证成功处理器
            authenticationFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
            // 设置认证成功时使用的自定义RememberMeServices，这与configure(HttpSecurity http)配置的RememberMeServices意义不一样
            authenticationFilter.setRememberMeServices(rememberMeServices());
            return authenticationFilter;
        }
    }
}
